﻿
namespace FATEFAKE
{
    #region using directives
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    #endregion

    class TestRunner
    {
        private FateCommands fateCommands;
        private Options options;

        private int countTestsRan;

        private int countFailedTests;

        protected string FfmegFullPath
        {
            get { return this.options.FfmpegExecutable; }
        }

        public int CountTestsRan
        {
            get { return this.countTestsRan; }
            set { this.countTestsRan = value; }
        }

        public int CountFailedTests
        {
            get { return this.countFailedTests; }
            set { this.countFailedTests = value; }
        }

        public int CountSucceededTests
        {
            get { return this.CountTestsRan - this.CountFailedTests; }
        }

        public TestRunner(Options options)
        {
            this.options = options;
        }

        public void InitializeCommands(string fileName)
        {
            this.fateCommands = FateCommands.CreateFromFile(fileName);
        }

        public bool RunTests()
        {
            this.CountTestsRan = 0;
            this.CountFailedTests = 0;
            foreach (var fateCommand in this.fateCommands.FateCommandList)
            {
                if (fateCommand.Command.StartsWith("run libavcodec/dct-test"))
                {
                    this.RunTestAndCount(this.RunInternalTest_Dct, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("run libavcodec/fft-test"))
                {
                    this.RunTestAndCount(this.RunInternalTest_Fft, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("run libavcodec/fft-fixed-test"))
                {
                    this.RunTestAndCount(this.RunInternalTest_FixedFft, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("ffmpeg"))
                {
                    this.RunTestAndCount(this.RunFFmpegTest, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("md5 -i"))
                {
                    this.RunTestAndCount(this.RunMd5FfmpegTest, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("crc "))
                {
                    this.RunTestAndCount(this.RunCrcFfmpegTest, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("pcm "))
                {
                    this.RunTestAndCount(this.RunPcmFfmpegTest, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("framecrc "))
                {
                    this.RunTestAndCount(this.RunFrameCrcFfmpegTest, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("framemd5 "))
                {
                    this.RunTestAndCount(this.RunFrameMd5FfmpegTest, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("enc_dec "))
                {
                    this.RunTestAndCount(this.RunEncDecTest, fateCommand);
                }
                else if (fateCommand.Command.StartsWith("enc_dec_pcm "))
                {
                    this.RunEncDecPcmTest(fateCommand);
                }
            }

            return this.CountFailedTests <= 0;
        }

        private bool RunTestAndCount(Func<FateCommand, bool> testFunc, FateCommand cmd)
        {
            bool success = testFunc(cmd);
            this.CountTestsRan++;
            if (success == false)
            {
                this.CountFailedTests++;
            }

            return success;
        }

        private bool RunInternalTest_Dct(FateCommand fateCommand)
        {
            return this.RunInternalTest(fateCommand, "-T DCT_TEST ");
        }

        private bool RunInternalTest_Fft(FateCommand fateCommand)
        {
            return this.RunInternalTest(fateCommand, "-T FFT_TEST ");
        }

        private bool RunInternalTest_FixedFft(FateCommand fateCommand)
        {
            return this.RunInternalTest(fateCommand, "-T FFTFIXED_TEST ");
        }

        private bool RunFFmpegTest(FateCommand fateCommand)
        {
            bool success = false;
            if (string.IsNullOrEmpty(fateCommand.Cmp))
            {
                string cmdLine = this.ReplaceSamplePaths(fateCommand.Command);
                string args = SkipFirstWord(cmdLine);
                string stdOutResult;
                int i = this.RunFfmpegAndGetStdOut(args, out stdOutResult);
                if (i != 0)
                {
                    this.LogTestResult(false, fateCommand);
                    return false;
                }

                success = this.CompareToReference(fateCommand, stdOutResult);
                this.LogTestResult(success, fateCommand);
            }
            else
            {
                if (string.CompareOrdinal(fateCommand.Cmp, "oneoff") == 0)
                {
                    string cmdLine = this.ReplaceSamplePaths(fateCommand.Command);
                    string args = SkipFirstWord(cmdLine).TrimEnd(new char[] { ' ', '-' });
                    string tempDestFile = this.GetDestFileName();
                    args += " \"" + this.AddProtocolIdentifier(tempDestFile) + "\"";
                    int r = this.RunFfmpeg(args);
                    if (r != 0)
                    {
                        this.LogTestResult(false, fateCommand);
                        return false;
                    }

                    success = this.CheckWithTinyPnsr(tempDestFile, TweakFateSuiteFileName(this.GetReferenceFileName(fateCommand)), fateCommand);
                    File.Delete(tempDestFile);
                    this.LogTestResult(success, fateCommand);
                }
            }

            return success;
        }

        private bool RunEncDecPcmTest(FateCommand fateCommand)
        {
            IList<string> splitArgs = SplitIntoParts(fateCommand.Command, ' ');
            string outfmt = splitArgs[1];
            string decfmt = splitArgs[2];
            string pcmfmt = splitArgs[3];
            string srcfile = splitArgs[4];
            if (srcfile.StartsWith("tests/"))
            {
                srcfile = Path.Combine(this.options.TestsPath, srcfile.Substring(6));
            }
            else if (srcfile.StartsWith("./tests/"))
            {
                srcfile = Path.Combine(this.options.TestsPath, srcfile.Substring(8));
            }
            else if (srcfile.StartsWith("fate-suite//"))
            {
                srcfile = Path.Combine(this.options.FateSuitePath, srcfile.Substring(12));
            }

            string encfile = this.GetDestFileName(outfmt);

            StringBuilder sb = new StringBuilder();
            sb.Append("-i ");
            sb.AppendFormat("\"{0}\"", this.AddProtocolIdentifier(srcfile));
            sb.Append(" ");
            for (int i = 5; i < splitArgs.Count; i++)
            {
                sb.Append(splitArgs[i]);
                sb.Append(" ");
            }

            sb.AppendFormat(" -f {0} -y \"{1}\"", outfmt, this.AddProtocolIdentifier(encfile));
            int r = this.RunFfmpeg(sb.ToString());
            if (r != 0)
            {
                this.LogTestResult(false, fateCommand);
                return false;
            }

            sb.Clear();
            sb.AppendFormat("-flags +bitexact -i \"{0}\" -c:a pcm_{1} -f {2} ", this.AddProtocolIdentifier(encfile), pcmfmt, decfmt);

            bool success = false;
            if (string.CompareOrdinal(fateCommand.Cmp, "stddev") == 0)
            {
                string tempDestFile = this.GetDestFileName();
                sb.Append(" -y ");
                sb.AppendFormat("\"{0}\"", this.AddProtocolIdentifier(tempDestFile));
                r = this.RunFfmpeg(sb.ToString());
                if (r != 0)
                {
                    this.LogTestResult(false, fateCommand);
                    return false;
                }

                success = this.CheckWithTinyPnsrStddev(srcfile, tempDestFile, fateCommand);
                File.Delete(tempDestFile);
            }
            else if (string.CompareOrdinal(fateCommand.Cmp, "oneoff") == 0)
            {
                string tempDestFile = this.GetDestFileName();
                sb.Append(" -y ");
                sb.AppendFormat("\"{0}\"", this.AddProtocolIdentifier(tempDestFile));
                r = this.RunFfmpeg(sb.ToString());
                if (r != 0)
                {
                    this.LogTestResult(false, fateCommand);
                    return false;
                }

                success = this.CheckWithTinyPnsrOneOff(srcfile, tempDestFile, fateCommand);
                File.Delete(tempDestFile);
            }
            else
            {
                sb.Append(" -");
                string stdOut;
                r = this.RunFfmpegAndGetStdOut(sb.ToString(), out stdOut);
                if (r != 0)
                {
                    this.LogTestResult(false, fateCommand);
                    return false;
                }

                success = this.CompareToFile(this.GetReferenceFileName(fateCommand), stdOut);
            }

            File.Delete(encfile);
            this.LogTestResult(success, fateCommand);
            return success;
        }

        private bool RunEncDecTest(FateCommand fateCommand)
        {
            const string FLAGS = "-flags +bitexact -sws_flags +accurate_rnd+bitexact";
            const string DEC_OPTS = "-threads 1 -idct simple " + FLAGS;
            const string ENC_OPTS = "-threads 1 -idct simple -dct fastint";
            IList<string> splitArgs = SplitIntoParts(fateCommand.Command, ' ');
            string src_fmt = splitArgs[1];
            string srcfile = splitArgs[2];
            if (srcfile.StartsWith("tests/"))
            {
                srcfile = Path.Combine(this.options.TestsPath, srcfile.Substring(6));
            }

            string enc_fmt = splitArgs[3];
            string enc_opt = splitArgs[4];
            string dec_fmt = splitArgs[5];
            string dec_opt = splitArgs[6];

            string tencfile = this.GetDestFileName(enc_fmt);
            string tdecfile = this.GetDestFileName(enc_fmt);
            string arguments = string.Format(
                                          "-f {0} {1} -i \"{2}\" {3} {4} {5} -f {6} -y \"{7}\" ",
                                          src_fmt,
                                          DEC_OPTS,
                                          this.AddProtocolIdentifier(srcfile),
                                          ENC_OPTS,
                                          enc_opt,
                                          FLAGS,
                                          enc_fmt,
                                          this.AddProtocolIdentifier(tencfile));
            int i = this.RunFfmpeg(arguments);
            if (i != 0)
            {
                this.LogTestResult(false, fateCommand);
                return false;
            }

            StringBuilder resultInfo = new StringBuilder();
            resultInfo.AppendLine(CalcMd5Sum(tencfile));
            resultInfo.AppendLine(this.GetFileSize(tencfile));

            string arg8 = splitArgs.Count > 8 ? splitArgs[8] : string.Empty;

            string arguments2 = string.Format(
                                            "{0} {1} -i \"{2}\" {3} {4} {5} -f {6} -y \"{7}\"",
                                            arg8,
                                            DEC_OPTS,
                                            this.AddProtocolIdentifier(tencfile),
                                            ENC_OPTS,
                                            dec_opt,
                                            FLAGS,
                                            dec_fmt,
                                            this.AddProtocolIdentifier(tdecfile));

            i = this.RunFfmpeg(arguments2);
            if (i != 0)
            {
                this.LogTestResult(false, fateCommand);
                return false;
            }

            resultInfo.AppendLine(CalcMd5Sum(tdecfile));
            resultInfo.AppendLine(this.GetTinyPnsrResult(srcfile, tdecfile, fateCommand));
            File.Delete(tdecfile);
            File.Delete(tencfile);

            bool success = this.EncDecCompareResult(resultInfo.ToString(), fateCommand);
            this.LogTestResult(success, fateCommand);
            return success;
        }

        private bool EncDecCompareResult(string result, FateCommand fateCommand)
        {
            string refFileName = this.GetReferenceFileName(fateCommand);
            using (TextReader trResult = new StringReader(result))
            {
                using (TextReader trReference = File.OpenText(refFileName))
                {
                    string lineResult = trResult.ReadLine();
                    string lineRef = trReference.ReadLine();
                    var m = Regex.Match(lineRef, "[a-z0-9]+");
                    if (!m.Success || string.CompareOrdinal(m.Value, lineResult) != 0)
                    {
                        return false;
                    }

                    lineResult = trResult.ReadLine();
                    lineRef = trReference.ReadLine();
                    m = Regex.Match(lineRef, "[0-9]+");
                    if (!m.Success || string.CompareOrdinal(m.Value, lineResult) != 0)
                    {
                        return false;
                    }

                    lineResult = trResult.ReadLine();
                    lineRef = trReference.ReadLine();
                    m = Regex.Match(lineRef, "[a-z0-9]+");
                    if (!m.Success || string.CompareOrdinal(m.Value, lineResult) != 0)
                    {
                        return false;
                    }

                    lineResult = trResult.ReadLine();
                    lineRef = trReference.ReadLine();
                    if (string.CompareOrdinal(lineResult, lineRef) != 0)
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary> 
        /// Splits the string passed in by the delimiters passed in. 
        /// Quoted sections are not split, and all tokens have whitespace 
        /// trimmed from the start and end. 
        private static List<string> SplitIntoParts(string stringToSplit, params char[] delimiters)
        {
            List<string> results = new List<string>();

            bool inQuote = false;
            StringBuilder currentToken = new StringBuilder();
            for (int index = 0; index < stringToSplit.Length; ++index)
            {
                char currentCharacter = stringToSplit[index];
                if (currentCharacter == '"')
                {
                    // When we see a ", we need to decide whether we are 
                    // at the start or send of a quoted section... 
                    inQuote = !inQuote;
                }
                else if (delimiters.Contains(currentCharacter) && inQuote == false)
                {
                    // We've come to the end of a token, so we find the token, 
                    // trim it and add it to the collection of results... 
                    string result = currentToken.ToString().Trim();
                    /*if (result != "")*/
                    results.Add(result);

                    for (; index < stringToSplit.Length - 1; index++)
                    {
                        if (!delimiters.Contains(stringToSplit[index + 1]))
                        {
                            break;
                        }
                    }

                    // We start a new token... 
                    currentToken = new StringBuilder();
                }
                else
                {
                    // We've got a 'normal' character, so we add it to 
                    // the curent token... 
                    currentToken.Append(currentCharacter);
                }
            }

            // We've come to the end of the string, so we add the last token... 
            string lastResult = currentToken.ToString().Trim();
            /*if (lastResult != "")*/
            results.Add(lastResult);

            return results;
        }

        private bool CheckWithTinyPnsr(string fileA, string fileB, FateCommand fateCommand)
        {
            string tiny_pnsr = Path.Combine(this.options.ToolsPath, "tiny_psnr.exe");
            string arguments = string.Format(
                                            "\"{0}\" \"{1}\" {2} {3}",
                                            fileA,
                                            fileB,
                                            string.IsNullOrEmpty(fateCommand.CmpUnit) ? "2" : fateCommand.CmpUnit,
                                            string.IsNullOrEmpty(fateCommand.CmpShift) ? "0" : fateCommand.CmpShift);
            string output;
            int r = this.RunCmdAndGetStdOut(tiny_pnsr, arguments, out output);
            if (r != 0)
            {
                return false;
            }

            return this.CompareTinyPsnrResult(output, "MAXDIFF");
        }

        private bool CheckWithTinyPnsrStddev(string fileA, string fileB, FateCommand fateCommand)
        {
            return CheckWithTinyPnsrParameter(fileA, fileB, "stddev", fateCommand);
        }

        private bool CheckWithTinyPnsrOneOff(string fileA, string fileB, FateCommand fateCommand)
        {
            return CheckWithTinyPnsrParameter(fileA, fileB, "MAXDIFF", fateCommand);
        }

        private bool CheckWithTinyPnsrParameter(string fileA, string fileB, string paramName, FateCommand fateCommand)
        {
            string tiny_pnsr = Path.Combine(this.options.ToolsPath, "tiny_psnr.exe");
            string arguments = string.Format(
                                        "\"{0}\" \"{1}\" {2} {3}",
                                        fileA,
                                        fileB,
                                        string.IsNullOrEmpty(fateCommand.CmpUnit) ? "2" : fateCommand.CmpUnit,
                                        string.IsNullOrEmpty(fateCommand.CmpShift) ? "0" : fateCommand.CmpShift);
            string output;
            int r = this.RunCmdAndGetStdOut(tiny_pnsr, arguments, out output);
            if (r != 0)
            {
                return false;
            }

            return this.CompareTinyPsnrResult(
                                    output,
                                    paramName,
                                    string.IsNullOrEmpty(fateCommand.Fuzz) ? 1 : Convert.ToDouble(fateCommand.Fuzz),
                                    string.IsNullOrEmpty(fateCommand.CmpTarget) ? 0 : Convert.ToDouble(fateCommand.CmpTarget));
        }

        private string GetTinyPnsrResult(string fileA, string fileB, FateCommand fateCommand)
        {
            string tiny_pnsr = Path.Combine(this.options.ToolsPath, "tiny_psnr.exe");
            string arguments = string.Format(
                                        "\"{0}\" \"{1}\" {2} {3}",
                                        fileA,
                                        fileB,
                                        string.IsNullOrEmpty(fateCommand.CmpUnit) ? "2" : fateCommand.CmpUnit,
                                        string.IsNullOrEmpty(fateCommand.CmpShift) ? "0" : fateCommand.CmpShift);
            string output;
            int r = this.RunCmdAndGetStdOut(tiny_pnsr, arguments, out output);
            if (r != 0)
            {
                return string.Empty;
            }

            return output;
        }

        private bool CompareTinyPsnrResult(string str, string identifier, double fuzz = 1, double targetValue = 0)
        {
            Match m = Regex.Match(str, "^.*" + identifier + ":\\s+(?<VALUE>[0-9.]+)");
            var v = m.Groups["VALUE"];
            if (v.Success)
            {
                double value = Convert.ToDouble(v.Value);
                value = Math.Abs(value - targetValue);
                return value <= fuzz ? true : false;
            }

            return false;
        }

        private string GetDestFileName(string extension)
        {
            return this.GetDestFileName() + "." + extension;
        }

        private string GetDestFileName()
        {
            return Path.Combine(this.options.TempPath, Guid.NewGuid().ToString());
        }

        private bool CompareToReference(FateCommand fateCommand, string result)
        {
            if (!string.IsNullOrEmpty(fateCommand.Cmp) && string.CompareOrdinal(fateCommand.Cmp, "oneline") == 0)
            {
                if (string.IsNullOrEmpty(fateCommand.Ref))
                {
                    return false;
                }

                return string.CompareOrdinal(fateCommand.Ref, result) == 0;
            }

            return this.CompareToFile(this.GetReferenceFileName(fateCommand), result);
        }

        private bool RunCrcFfmpegTest(FateCommand fateCommand)
        {
            return this.RunFfmpegBasedTest(fateCommand, "-f crc -");
        }

        private bool RunPcmFfmpegTest(FateCommand fateCommand)
        {
            string tempDestFile = this.GetDestFileName();// Path.Combine(this.options.TempPath, fateCommand.TestNameConsolidated);
            string cmdLine = SkipFirstWord(fateCommand.Command);
            cmdLine = this.ReplaceSamplePaths(cmdLine);
            cmdLine += " -vn -f s16le -y \"" + this.AddProtocolIdentifier(tempDestFile) + "\"";
            int i = this.RunFfmpeg(cmdLine);
            if (i != 0)
            {
                this.LogTestResult(false, fateCommand);
                return false;
            }

            bool success;
            if (string.CompareOrdinal(fateCommand.Cmp, "oneoff") == 0)
            {
                success = this.CheckWithTinyPnsrOneOff(TweakFateSuiteFileName(this.GetReferenceFileName(fateCommand)), tempDestFile, fateCommand);
            }
            else if (string.CompareOrdinal(fateCommand.Cmp, "stddev") == 0)
            {
                success = this.CheckWithTinyPnsrStddev(TweakFateSuiteFileName(this.GetReferenceFileName(fateCommand)), tempDestFile, fateCommand);
            }
            else
            {
                this.LogTestResult(false, fateCommand);
                return false;
            }

            File.Delete(tempDestFile);
            this.LogTestResult(success, fateCommand);
            return success;
        }

        private bool RunFrameCrcFfmpegTest(FateCommand fateCommand)
        {
            return this.RunFfmpegBasedTest(fateCommand, "-f framecrc -");
        }

        private bool RunFrameMd5FfmpegTest(FateCommand fateCommand)
        {
            return this.RunFfmpegBasedTest(fateCommand, "-f framemd5 -");
        }

        private bool RunFfmpegBasedTest(FateCommand fateCommand, string cmdLineSuffix)
        {
            string cmdLine = SkipFirstWord(fateCommand.Command);
            cmdLine = this.ReplaceSamplePaths(cmdLine);
            cmdLine += " " + cmdLineSuffix;
            string stdOutResult;
            int i = this.RunFfmpegAndGetStdOut(cmdLine, out stdOutResult);
            if (i != 0)
            {
                this.LogTestResult(false, fateCommand);
                return false;
            }

            bool success = this.CompareToReference(fateCommand, stdOutResult);
            this.LogTestResult(success, fateCommand);
            return success;
        }

        private bool RunMd5FfmpegTest(FateCommand fateCommand)
        {
            string cmdLine = SkipFirstWord(fateCommand.Command);
            cmdLine = this.ReplaceSamplePaths(cmdLine);
            cmdLine = cmdLine + " -";
            byte[] md5Hash;
            int i = this.RunFfmpegAndMd5StdOut(cmdLine, out md5Hash);
            if (i != 0)
            {
                this.LogTestResult(false, fateCommand);
                return false;
            }

            string result = ToHexString(md5Hash);
            bool success = this.CompareToReference(fateCommand, result);
            this.LogTestResult(success, fateCommand);
            return success;
        }

        private bool CompareToFile(string refFileName, string stdOutResult)
        {
            using (TextReader tr2 = new StringReader(stdOutResult))
            {
                using (TextReader tr = File.OpenText(refFileName))
                {
                    for (; ; )
                    {
                        string a = tr.ReadLine();
                        string b = tr2.ReadLine();
                        if (a == null && b != null || a != null && b == null)
                        {
                            return false;
                        }

                        if (a == null && b == null)
                        {
                            return true;
                        }

                        if (System.String.Compare(a, b, System.StringComparison.Ordinal) != 0)
                        {
                            return false;
                        }
                    }
                }
            }
        }

        private string GetReferenceFileName(FateCommand fateCommand)
        {
            string str;
            if (string.IsNullOrEmpty(fateCommand.Ref))
            {
                str = Path.Combine(this.options.ReferencePath, fateCommand.TestNameConsolidated);
            }
            else
            {
                if (fateCommand.Ref.StartsWith("./tests/ref/fate/"))
                {
                    str = Path.Combine(this.options.ReferencePath, StripOff(fateCommand.Ref, "./tests/ref/fate/"));
                }
                else if (fateCommand.Ref.StartsWith("./tests/ref/"))
                {
                    str = Path.Combine(this.options.ReferencePath, StripOff(fateCommand.Ref, "./tests/ref/"));
                    if (!File.Exists(str))
                    {
                        str = Path.Combine(this.options.TestsPath + @"\ref\", StripOff(fateCommand.Ref, "./tests/ref/"));
                    }
                }
                else
                {
                    str = Path.Combine(this.options.ReferencePath, fateCommand.Ref);
                }
            }

            return str;
        }

        private string ReplaceSamplePaths(string str)
        {
            StringBuilder sb = new StringBuilder();
            string[] parts = str.Split(' ');
            foreach (var part in parts)
            {
                if (part.Contains("/data/"))
                {
                    sb.AppendFormat("\"{0}\"", this.AddProtocolIdentifier(TweakDataFileName(part)));
                }
                else if (part.Contains("fate-suite/"))
                {
                    sb.AppendFormat("\"{0}\"", this.AddProtocolIdentifier(TweakFateSuiteFileName(part)));
                }
                else if (part.Contains("/tests/"))
                {
                    sb.AppendFormat("\"{0}\"", this.AddProtocolIdentifier(TweakTestsFileName(part)));
                }
                else
                {
                    sb.Append(part);
                }

                sb.Append(" ");
            }

            return sb.ToString();
        }

        private bool RunInternalTest(FateCommand fateCommand, string testArgument)
        {
            string cmdArguments = SkipFirstTwoWords(fateCommand.Command);
            int r = this.RunFfmpeg(testArgument + cmdArguments);
            bool success = r == 0;
            this.LogTestResult(success, fateCommand);
            return success;
        }

        private int RunFfmpegAndMd5StdOut(string cmdArguments, out byte[] md5Hash)
        {
            return this.RunCmdAndMd5StdOut(this.FfmegFullPath, this.TweakAndPrintFfmpegCmdLine(cmdArguments), out md5Hash);
        }

        private int RunFfmpegAndGetStdOut(string cmdArguments, out string stdOut)
        {
            return this.RunCmdAndGetStdOut(this.FfmegFullPath, this.TweakAndPrintFfmpegCmdLine(cmdArguments), out stdOut);
        }

        private int RunFfmpeg(string cmdArguments)
        {
            return this.RunCmd(this.FfmegFullPath, this.TweakAndPrintFfmpegCmdLine(cmdArguments));
        }

        private int RunCmd(string exeFilename, string cmdArguments)
        {
            Process externalProcess = new Process();
            externalProcess.StartInfo.FileName = exeFilename;
            externalProcess.StartInfo.Arguments = cmdArguments;
            externalProcess.StartInfo.UseShellExecute = false;
            if (this.options.Silent)
            {
                externalProcess.StartInfo.RedirectStandardOutput = true;
                externalProcess.StartInfo.RedirectStandardError = true;
            }

            externalProcess.Start();
            using (JobHelper jobHelper = new JobHelper(externalProcess))
            {
                if (this.options.Silent)
                {
                    externalProcess.BeginOutputReadLine();
                    externalProcess.BeginErrorReadLine();
                }

                externalProcess.WaitForExit();
            }

            return externalProcess.ExitCode;
        }

        private int RunCmdAndMd5StdOut(string exeFilename, string cmdArguments, out byte[] md5Hash)
        {
            Process externalProcess = new Process();
            externalProcess.StartInfo.FileName = exeFilename;
            externalProcess.StartInfo.Arguments = cmdArguments;
            externalProcess.StartInfo.UseShellExecute = false;
            externalProcess.StartInfo.RedirectStandardOutput = true;
            if (this.options.Silent)
            {
                externalProcess.StartInfo.RedirectStandardError = true;
            }

            externalProcess.Start();
            using (JobHelper jobHelper = new JobHelper(externalProcess))
            {
                StringBuilder sb = new StringBuilder();

                if (this.options.Silent)
                {
                    externalProcess.OutputDataReceived += (sender, outLine) => sb.AppendLine(outLine.Data);
                }
                else
                {
                    externalProcess.OutputDataReceived += (sender, outLine) =>
                        {
                            if (!string.IsNullOrEmpty(outLine.Data))
                            {
                                sb.AppendLine(outLine.Data);
                                Console.WriteLine(outLine.Data);
                            }
                        };
                }

                if (this.options.Silent)
                {
                    externalProcess.BeginErrorReadLine();
                }

                System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                md5Hash = md5.ComputeHash(externalProcess.StandardOutput.BaseStream);

                externalProcess.WaitForExit();
            }

            return externalProcess.ExitCode;

        }

        private int RunCmdAndGetStdOut(string exeFilename, string cmdArguments, out string stdOut)
        {
            Process externalProcess = new Process();
            externalProcess.StartInfo.FileName = exeFilename;
            externalProcess.StartInfo.Arguments = cmdArguments;
            externalProcess.StartInfo.UseShellExecute = false;
            externalProcess.StartInfo.RedirectStandardOutput = true;
            if (this.options.Silent)
            {
                externalProcess.StartInfo.RedirectStandardError = true;
            }

            externalProcess.Start();
            using (JobHelper jobHelper = new JobHelper(externalProcess))
            {
                StringBuilder sb = new StringBuilder();

                if (this.options.Silent)
                {
                    externalProcess.OutputDataReceived += (sender, outLine) =>
                        {
                            if (!string.IsNullOrEmpty(outLine.Data))
                            {
                                sb.AppendLine(outLine.Data);
                            }
                        };
                }
                else
                {
                    externalProcess.OutputDataReceived += (sender, outLine) =>
                        {
                            if (!string.IsNullOrEmpty(outLine.Data))
                            {
                                sb.AppendLine(outLine.Data);
                                Console.WriteLine(outLine.Data);
                            }
                        };
                }

                externalProcess.BeginOutputReadLine();
                if (this.options.Silent)
                {
                    externalProcess.BeginErrorReadLine();
                }

                externalProcess.WaitForExit();
                stdOut = sb.ToString();
            }

            return externalProcess.ExitCode;
        }

        private void LogTestResult(bool success, FateCommand fateCommand)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Test \"{0}\" -> {1}", fateCommand.TestNameConsolidated, success ? "OK" : "FAILURE");
            Console.WriteLine(sb);
        }

        private string TweakDataFileName(string str)
        {
            int i = str.LastIndexOf('/');
            string newFilename = Path.Combine(this.options.DataPath, str.Substring(i + 1));
            return newFilename;
        }

        private string TweakTestsFileName(string str)
        {
            int i = str.LastIndexOf("/tests/");
            string newFilename = Path.Combine(this.options.TestsPath, str.Substring(i + 6 + 1));
            return newFilename;
        }


        private string TweakFateSuiteFileName(string str)
        {
            int i = str.LastIndexOf("fate-suite/");
            string newFilename = Path.Combine(this.options.FateSuitePath, str.Substring(i + 11 + 1));
            return newFilename;
        }

        private static string SkipFirstWord(string str)
        {
            Match m = Regex.Match(str, "^[a-zA-Z0-9\\\\/-]+\\s+(?<CMDS>.*)");
            var cmds = m.Groups["CMDS"];
            if (cmds.Success)
            {
                return cmds.Value;
            }

            return string.Empty;

        }

        private static string SkipFirstTwoWords(string str)
        {
            Match m = Regex.Match(str, "^[a-zA-Z0-9\\\\/-]+\\s+[a-zA-Z0-9\\\\/-]+\\s+(?<CMDS>.*)");
            var cmds = m.Groups["CMDS"];
            if (cmds.Success)
            {
                return cmds.Value;
            }

            return string.Empty;
        }

        private static string ToHexString(byte[] bytes)
        {
            return string.Concat(bytes.Select(b => b.ToString("x2")));
        }

        private static string StripOff(string str, string startsWith)
        {
            if (str.StartsWith(startsWith))
            {
                return str.Substring(startsWith.Length);
            }

            return str;
        }

        private static string CalcMd5Sum(string file)
        {
            using (var fileStream = File.OpenRead(file))
            {
                System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                string s = BitConverter.ToString(md5.ComputeHash(fileStream)).Replace("-", string.Empty).ToLowerInvariant();
                return s;
            }
        }

        private string GetFileSize(string file)
        {
            FileInfo fileInfo = new FileInfo(file);
            return Convert.ToString(fileInfo.Length);
        }

        private string TweakAndPrintFfmpegCmdLine(string cmdLine)
        {
            string cmdLineOut = cmdLine;
            if (!string.IsNullOrEmpty(this.options.PrependFfmpegCmdLine))
            {
                if (!cmdLine.StartsWith("-T"))
                {
                    cmdLineOut = this.options.PrependFfmpegCmdLine + " " + cmdLine;
                }
            }

            if (this.options.PrintFfmpegCommandLine)
            {
                Console.WriteLine(">FFMPEG {0}<", cmdLineOut);
            }

            return cmdLineOut;
        }

        private string AddProtocolIdentifier(string path)
        {
            if (this.options.UseWinfile == true)
            {
                return "winfile:" + path;
            }

            return path;
        }
    }
}
